# gozerlib/plugins.py
#
#

""" holds all the plugins. plugins are imported modules. """

## gozerlib imports

from commands import cmnds
from eventbase import EventBase
from config import cfg
from persist.persist import Persist
from admin import plugin_packages, default_plugins, pluginlist
from utils.lazydict import LazyDict

## basic imports

import os
import logging
import Queue
import copy
import sys

cpy = copy.deepcopy

class Plugins(object):

    """ the plugins object contains all the plugins. """

    def __init__(self, filename):
        #Persist.__init__(self, filename)

        #if not self.data:
        #    self.data = LazyDict()

        #if not self.data.periodicals:
        #    self.data.periodicals = []
        self.periodicals = []
        self.store = {}
        
    def __getitem__(self, item):

        """
            return plugin. 

            :param item: plugin name to retur
            :type item: string
            :rtype: module or None

        """

        if self.store.has_key(item):
            return self.store[item]
        else:
            return None

    def periodical(self):

        
        for plugname in self.periodicals:

            self.reload(plugname)

            try:
                self.store[plugname].periodical()
            except (AttributeError, KeyError):
                logging.error("no periodical implemented in plugin: %s" % plugname)

    def exists(self, plugname):

        """ 
            see if plugin exists.

            :param plugname: name of plugin to check
            :type plugname: string
            :rtype: boolean

        """

        for name, plugin in self.store.iteritems():
            if plugname in name:
                return True

        return False

    def loadall(self, paths=[], force=False):

        """
            load all plugins from given paths, if force is true .. otherwise load 
            all plugins for default_plugins list.

            :param paths: paths of modules to search plugins in
            :type paths: list of modules
            :param force: whether to force loading of all found plugins
            :type force: boolean 
            :rtype: None

        """

        if not paths:
            paths = plugin_packages

        if not force:
            logging.debug('on demand plug loading is enabled')

            for plug in default_plugins:
                self.load(plug)

            return

        for path in paths:
            p = path.replace('.', os.sep)
            for plug in os.listdir(p):

                if plug.endswith(".py"):
                    mod = ".".join(path.split(os.sep))
                    modname = "%s.%s" % (mod, plug[:-3])
                    self.load(modname)

    def unload(self, modname):

        """ 
            unload plugin .. remove related commands from cmnds object. 

            :param modname: module name of the plugin to remove
            :type modname: string
            :rtype: boolean

        """

        try:
            cmnds.unload(modname)
        except KeyError:
            return False

        try:
            self.store[modname].shutdown()
            logging.debug('called %s shutdown' % modname)
        except (AttributeError, KeyError):
            pass

        return True

    def load(self, modname):

        """ 
            load a plugin. 

            :param modname: module name of plugin to load
            :type modname: string
            :rtype: module (the plugin)

        """

        logging.debug("loading %s" % modname)
        mod = __import__(modname)
        self.store[modname] = sys.modules[modname]

        try:
            self.store[modname].init()
            logging.debug('%s init called' % modname)
        except AttributeError:
            pass

        return self.store[modname]

    def reload(self, modname):

        """
            reload a plugin. just load for now. 

            :param modname: module name of the plugin to reload
            :type modname: string
            :rtype: module (the reloaded plugin)

        """

        self.unload(modname)
        return self.load(modname)

    def dispatch(self, bot, event, *args, **kwargs):

        """
            dispatch event onto the cmnds object. check for pipelines first.

            :param bot: bot on which event took place
            :type bot: gozerlib.botbase,BotBase
            :param event: the event that triggered the command
            :type event: gozerlib.eventbase.EventBase
            :rtype: None

        """

        if ' | ' in event.txt:
            self.pipelined(bot, event, *args, **kwargs)

        else:
            bevent = cpy(event)
            self.needreloadcheck(bot, event)

            if not cmnds.dispatch(bot, event, *args, **kwargs):

               if event.txt in pluginlist():
                   bevent.txt = 'help-plug ' + event.txt
                   bevent.makeargs()
                   self.needreloadcheck(bot, bevent)
                   
                   if cmnds.dispatch(bot, bevent, *args, **kwargs):
                       return True

               #event.reply(u'no such command: ' + event.txt)
              
    def pipelined(self, bot, event, *args, **kwargs):

        """ 
            split cmnds, create events for them, 
            chain the queues and dispatch. 

            :param bot: bot on which event took place
            :type bot: gozerlib.botbase,BotBase
            :param event: the event that triggered the command
            :type event: gozerlib.eventbase.EventBase
            :rtype: None

        """

        origqueues = event.queues
        event.queues = []
        events = []

        # split commands
        for item in event.txt.split(' | '):
            e = copy.deepcopy(event)
            e.txt = item.strip()
            logging.debug('creating event for %s' % e.txt)
            e.bot = bot
            e.makeargs()
            events.append(e)

        # loop over events .. chain queues
        prevq = None

        for e in events[:-1]:
            q = Queue.Queue()
            e.queues.append(q)
            if prevq:
                e.inqueue = prevq
            prevq = q

        events[-1].inqueue = prevq

        if origqueues:
            events[-1].queues = origqueues

        # do the dispatch
        for e in events:
            self.dispatch(bot, e)

    def list(self):

        """ return list of all plugins. """

        return [name for name in self.store]

    def needreloadcheck(self, bot, event, target=None):

        """
            check if event requires a plugin to be reloaded. if so 
            reload the plugin.

            :param bot: bot on which event took place
            :type bot: gozerlib.botbase,BotBase
            :param event: the event that triggered the command
            :type event: gozerlib.eventbase.EventBase
            :rtype: module (the reloaded plugin)

        """

        plugloaded = None

        if cfg['ondemand']:
            cmndtable = Persist('cmndtable').data
            try:

                if target:
                    logging.info('target set: %s' % target)
                    cmnd = 'target-set'
                    plugin = target

                else:
                    from aliases import aliasget
                    t = event.txt.split()[0]
                    alias = aliasget(t)
                    if alias:
                        alias = alias.split()[0]
                    cmnd = alias or t
                    plugin = cmndtable[cmnd]

                logging.info('cmnd: %s plugin: %s' % (cmnd, plugin))

                if self.exists(plugin):
                    return False

                plugloaded = self.reload(plugin)

                logging.info('reloaded %s' % str(plugin))

            except KeyError, ex:
               logging.info("can't find plugin to reload for %s" % event.txt.split()[0])

        return plugloaded

# the plugins object (plugs)
plugs = Plugins('gozerstore/plugins')
